package com.shermanzhou.mock;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * @author zhouchaoqun
 * @date 2019/4/12 11:57 AM
 */
@RunWith(MockitoJUnitRunner.class)
public class HelloMock {

    @Test
    public void verifyBehavior() {
        // mock creation
        List<String> mockList = mock(List.class);

        // using mock object
        // mock will remember all interactions
        mockList.add("one");
        mockList.clear();

        // verification
        // verify interactions
        verify(mockList).add("one");
        verify(mockList).clear();
    }

    @Test
    public void stubbing() {
        // you can mock concrete classes, not just interfaces
        LinkedList mockList = mock(LinkedList.class);

        // stubbing
        when(mockList.get(0)).thenReturn("first");
        when(mockList.get(0)).thenReturn("first1");
        when(mockList.get(1)).thenThrow(new RuntimeException("haaaaaa"));
        when(mockList.get(1)).thenThrow(new RuntimeException("Haaaaaa"));

        // following prints "first"
        System.out.println(mockList.get(0));

        // following throws runtime exception
        System.out.println(mockList.get(1));

        // following prints "null" because get(999) was not stubbed
        System.out.println(mockList.get(999));

        verify(mockList).get(0);
        verify(mockList).get(1);
        verify(mockList).get(999);
    }

    @Test
    public void argMatchers() {
        List<String> mockList = mock(List.class);

        // stubbing using built-in anyInt() argument matcher
        when(mockList.get(anyInt())).thenReturn("element");

        // stubbing using custom argument matcher
        when(mockList.contains(argThat(isValid()))).thenReturn(true);

        System.out.println(mockList.get(999));

        mockList.add("element");

        System.out.println(mockList.contains(isValid()));

        // you can also verify using an argument matcher
        verify(mockList).get(anyInt());

        verify(mockList).add(argThat(someString -> someString.length() > 5));
    }

    @Test
    public void verifyExactNumberOfInvocations() {
        List mockList = mock(List.class);

        mockList.add("once");

        mockList.add("twice");
        mockList.add("twice");

        mockList.add("three times");
        mockList.add("three times");
        mockList.add("three times");

        verify(mockList, times(1)).add("once");
        verify(mockList, times(2)).add("twice");
        verify(mockList, times(3)).add("three times");

        verify(mockList, never()).add("never");

        verify(mockList, atLeastOnce()).add("once");
        verify(mockList, atLeast(2)).add("twice");
        verify(mockList, atMost(3)).add("twice");
    }

    @Test
    public void stubbingVoidMethodsWithExceptions() {
        List mockList = mock(List.class);

        doThrow(new RuntimeException("mock list clear")).when(mockList).clear();

        mockList.clear();
    }

    @Test
    public void verificationInOrder() {
        List singleMock = mock(List.class);

        singleMock.add("add first");
        singleMock.add("add second");

        InOrder inOrder = inOrder(singleMock);

        inOrder.verify(singleMock).add("add first");
        inOrder.verify(singleMock).add("add second");

        List firstMock = mock(List.class);
        List secondMock = mock(List.class);

        firstMock.add("was called first");
        secondMock.add("was called second");

        InOrder inOrder1 = inOrder(firstMock, secondMock);

        inOrder1.verify(firstMock).add("was called first");
        inOrder1.verify(secondMock).add("was called second");

        InOrder inOrder2 = inOrder(firstMock, singleMock);
        inOrder2.verify(singleMock).add("add first");
        inOrder2.verify(singleMock).add("add second");
        inOrder2.verify(firstMock).add("was called first");
    }

    @Test
    public void findRedundantInvocations() {
        List mockList = mock(List.class);

        mockList.add("one");
        mockList.add("two");

        verify(mockList).add("one");
        verify(mockList).add("two");

        verifyNoMoreInteractions(mockList);
    }

    @Mock
    private List mockList;

//    @Before
//    public void init() {
//        MockitoAnnotations.initMocks(this);
//    }

    @Test
    public void stubbingConsecutiveCalls() {
        when(mockList.get(anyInt()))
                .thenReturn("one", "two", "three")
                .thenThrow(new RuntimeException("empty list"));

        System.out.println(mockList.get(0));
        System.out.println(mockList.get(1));
        System.out.println(mockList.get(2));
        try {
            System.out.println(mockList.get(0));
        } catch (RuntimeException e) {
            assertEquals("empty list", e.getMessage());
        }
    }

    @Test
    public void stubbingOverride() {
        when(mockList.get(anyInt()))
                .thenReturn("one", "two", "three");
        when(mockList.get(anyInt()))
                .thenThrow(new RuntimeException("empty list"));
        try {
            System.out.println(mockList.get(0));
        } catch (RuntimeException e) {
            assertEquals("empty list", e.getMessage());
        }
    }

    @Test
    public void stubbingWithCallbacks() {
        when(mockList.get(anyInt()))
                .thenAnswer(invocation -> {
                    Object[] arguments = invocation.getArguments();
                    Method method = invocation.getMethod();
                    Object mock = invocation.getMock();
                    System.out.println(method);
                    System.out.println(mock);
                    return "called with argumants: " + arguments[0];
                });

        System.out.println(mockList.get(23));
    }

    @Test
    public void spyingOnRealObjects() {
        List<String> list = new LinkedList<>();
        list = spy(list);
        when(list.size()).thenReturn(100);

        list.add("1");
        list.add("2");
        list.add("3");

        doReturn("three").when(list).get(3);

        System.out.println(list.get(0));
        System.out.println(list.get(1));
        System.out.println(list.get(2));

        System.out.println(list.size());

        verify(list, atLeast(1)).get(anyInt());

        System.out.println(list.get(3));
    }

    @Test
    public void changingDefaultReturnValuesOfUnstubbedInvocations() {
        User mockUser = mock(User.class, Answers.RETURNS_SMART_NULLS);
        System.out.println(mockUser.getId());
        System.out.println(mockUser.findById("0"));
    }

    @Test
    public void CapturingArgumentsForFurtherAssertions() {
        User user = mock(User.class, Answers.CALLS_REAL_METHODS);
        user.createUser("hehe", 12);

        ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
        ArgumentCaptor<Integer> ageCaptor = ArgumentCaptor.forClass(Integer.class);
        verify(user).createUser(nameCaptor.capture(), ageCaptor.capture());
        assertEquals("hehe", nameCaptor.getValue());
        assertEquals(12, (int) ageCaptor.getValue());
    }



    private <T> ArgumentMatcher<T> isValid() {
        return o -> true;
    }

}
